core: Add --union mode to checkout
authorColin Walters <walters@verbum.org>
Tue, 6 Mar 2012 16:37:50 +0000 (11:37 -0500)
committerColin Walters <walters@verbum.org>
Tue, 6 Mar 2012 16:59:06 +0000 (11:59 -0500)
This is another step towards ostbuild using this instead of the
"compose" builtin.

src/libostree/ostree-core.c
src/libostree/ostree-core.h
src/libostree/ostree-repo.c
src/libostree/ostree-repo.h
src/ostree/ot-builtin-checkout.c
tests/t0000-basic.sh

index 8a4cb1fd599f133a5ee564082c8d08e79e6937d5..5f938d1179237d3a2debdb69e1acab645dea224e 100644 (file)
@@ -915,6 +915,9 @@ ostree_create_temp_file_from_input (GFile            *dir,
   /* 128 attempts seems reasonable... */
   for (i = 0; i < 128; i++)
     {
+      if (g_cancellable_set_error_if_cancelled (cancellable, error))
+        goto out;
+
       g_free (possible_name);
       possible_name = subst_xxxxxx (tmp_name->str);
       g_clear_object (&possible_file);
@@ -941,7 +944,7 @@ ostree_create_temp_file_from_input (GFile            *dir,
           break;
         }
     }
-  if (i == 128)
+  if (i >= 128)
     {
       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                    "Exhausted 128 attempts to create a temporary file");
@@ -990,3 +993,64 @@ ostree_create_temp_regular_file (GFile            *dir,
   g_clear_object (&ret_stream);
   return ret;
 }
+
+gboolean
+ostree_create_temp_hardlink (GFile            *dir,
+                             GFile            *src,
+                             const char       *prefix,
+                             const char       *suffix,
+                             GFile           **out_file,
+                             GCancellable     *cancellable,
+                             GError          **error)
+{
+  gboolean ret = FALSE;
+  GString *tmp_name = NULL;
+  char *possible_name = NULL;
+  GFile *possible_file = NULL;
+  int i = 0;
+
+  tmp_name = create_tmp_string (ot_gfile_get_path_cached (dir),
+                                prefix, suffix);
+  
+  /* 128 attempts seems reasonable... */
+  for (i = 0; i < 128; i++)
+    {
+      if (g_cancellable_set_error_if_cancelled (cancellable, error))
+        goto out;
+
+      g_free (possible_name);
+      possible_name = subst_xxxxxx (tmp_name->str);
+      g_clear_object (&possible_file);
+      possible_file = g_file_get_child (dir, possible_name);
+
+      if (link (ot_gfile_get_path_cached (src), ot_gfile_get_path_cached (possible_file)) < 0)
+        {
+          if (errno == EEXIST)
+            continue;
+          else
+            {
+              ot_util_set_error_from_errno (error, errno);
+              goto out;
+            }
+        }
+      else
+        {
+          break;
+        }
+    }
+  if (i >= 128)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Exhausted 128 attempts to create a temporary file");
+      goto out;
+    }
+
+  ret = TRUE;
+  ot_transfer_out_value(out_file, &possible_file);
+ out:
+  if (tmp_name)
+    g_string_free (tmp_name, TRUE);
+  g_free (possible_name);
+  g_clear_object (&possible_file);
+  return ret;
+}
index 24489ce3de898a311b031623f49f4229abf6a487..e6893c924d7d091c40dc5ac60f087487bb3684c4 100644 (file)
@@ -182,6 +182,14 @@ gboolean ostree_create_temp_regular_file (GFile            *dir,
                                           GCancellable     *cancellable,
                                           GError          **error);
 
+gboolean ostree_create_temp_hardlink (GFile            *dir,
+                                      GFile            *src,
+                                      const char       *prefix,
+                                      const char       *suffix,
+                                      GFile           **out_file,
+                                      GCancellable     *cancellable,
+                                      GError          **error);
+
 GVariant *ostree_create_archive_file_metadata (GFileInfo   *file_info,
                                                GVariant    *xattrs);
 
index 8087f060229e8eb8685c008e142371f37111f9e1..05ebbc4abc494d77b2b8581a501fd18e7bd84fca 100644 (file)
@@ -2453,6 +2453,7 @@ ostree_repo_iter_objects (OstreeRepo  *self,
 static gboolean
 checkout_file_from_input (GFile          *file,
                           OstreeRepoCheckoutMode mode,
+                          OstreeRepoCheckoutOverwriteMode    overwrite_mode,
                           GFileInfo      *finfo,
                           GVariant       *xattrs,
                           GInputStream   *input,
@@ -2460,6 +2461,9 @@ checkout_file_from_input (GFile          *file,
                           GError        **error)
 {
   gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  GFile *dir = NULL;
+  GFile *temp_file = NULL;
   GFileInfo *temp_info = NULL;
 
   if (mode == OSTREE_REPO_CHECKOUT_MODE_USER)
@@ -2475,20 +2479,119 @@ checkout_file_from_input (GFile          *file,
       xattrs = NULL;
     }
 
-  if (!ostree_create_file_from_input (file, temp_info ? temp_info : finfo,
-                                      xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE,
-                                      NULL, cancellable, error))
-    goto out;
+  if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
+    {
+      if (g_file_info_get_file_type (temp_info ? temp_info : finfo) == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!ostree_create_file_from_input (file, temp_info ? temp_info : finfo,
+                                              xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE,
+                                              NULL, cancellable, &temp_error))
+            {
+              if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+                {
+                  g_clear_error (&temp_error);
+                }
+              else
+                {
+                  g_propagate_error (error, temp_error);
+                  goto out;
+                }
+            }
+        }
+      else
+        {
+          dir = g_file_get_parent (file);
+          if (!ostree_create_temp_file_from_input (dir, NULL, "checkout",
+                                                   temp_info ? temp_info : finfo,
+                                                   xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE,
+                                                   &temp_file, NULL,
+                                                   cancellable, error))
+            goto out;
+          
+          if (rename (ot_gfile_get_path_cached (temp_file), ot_gfile_get_path_cached (file)) < 0)
+            {
+              ot_util_set_error_from_errno (error, errno);
+              goto out;
+            }
+        }
+    }
+  else
+    {
+      if (!ostree_create_file_from_input (file, temp_info ? temp_info : finfo,
+                                          xattrs, input, OSTREE_OBJECT_TYPE_RAW_FILE,
+                                          NULL, cancellable, error))
+        goto out;
+    }
 
   ret = TRUE;
  out:
   g_clear_object (&temp_info);
+  g_clear_object (&temp_file);
+  g_clear_object (&dir);
+  return ret;
+}
+
+static gboolean
+checkout_file_hardlink (OstreeRepo                  *self,
+                        OstreeRepoCheckoutMode    mode,
+                        OstreeRepoCheckoutOverwriteMode    overwrite_mode,
+                        GFile                    *source,
+                        GFile                    *destination,
+                        GCancellable             *cancellable,
+                        GError                  **error)
+{
+  gboolean ret = FALSE;
+  GFile *dir = NULL;
+  GFile *temp_file = NULL;
+
+  if (overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
+    {
+      dir = g_file_get_parent (destination);
+      if (!ostree_create_temp_hardlink (dir, (GFile*)source, NULL, "link",
+                                        &temp_file, cancellable, error))
+        goto out;
+
+      /* Idiocy, from man rename(2)
+       *
+       * "If oldpath and newpath are existing hard links referring to
+       * the same file, then rename() does nothing, and returns a
+       * success status."
+       *
+       * So we can't make this atomic.  
+       */
+
+      (void) unlink (ot_gfile_get_path_cached (destination));
+
+      if (rename (ot_gfile_get_path_cached (temp_file),
+                  ot_gfile_get_path_cached (destination)) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      g_clear_object (&temp_file);
+    }
+  else
+    {
+      if (link (ot_gfile_get_path_cached (source), ot_gfile_get_path_cached (destination)) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_object (&dir);
+  if (temp_file)
+    (void) unlink (ot_gfile_get_path_cached (temp_file));
+  g_clear_object (&temp_file);
   return ret;
 }
 
 gboolean
 ostree_repo_checkout_tree (OstreeRepo               *self,
                            OstreeRepoCheckoutMode    mode,
+                           OstreeRepoCheckoutOverwriteMode    overwrite_mode,
                            GFile                    *destination,
                            OstreeRepoFile           *source,
                            GFileInfo                *source_info,
@@ -2511,7 +2614,7 @@ ostree_repo_checkout_tree (OstreeRepo               *self,
   if (!ostree_repo_file_get_xattrs (source, &xattrs, NULL, error))
     goto out;
 
-  if (!checkout_file_from_input (destination, mode, source_info,
+  if (!checkout_file_from_input (destination, mode, overwrite_mode, source_info,
                                  xattrs, NULL,
                                  cancellable, error))
     goto out;
@@ -2541,7 +2644,8 @@ ostree_repo_checkout_tree (OstreeRepo               *self,
 
       if (type == G_FILE_TYPE_DIRECTORY)
         {
-          if (!ostree_repo_checkout_tree (self, mode, dest_path, (OstreeRepoFile*)src_child, file_info,
+          if (!ostree_repo_checkout_tree (self, mode, overwrite_mode,
+                                          dest_path, (OstreeRepoFile*)src_child, file_info,
                                           cancellable, error))
             goto out;
         }
@@ -2554,11 +2658,8 @@ ostree_repo_checkout_tree (OstreeRepo               *self,
               g_clear_object (&object_path);
               object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT);
 
-              if (link (ot_gfile_get_path_cached (object_path), ot_gfile_get_path_cached (dest_path)) < 0)
-                {
-                  ot_util_set_error_from_errno (error, errno);
-                  goto out;
-                }
+              if (!checkout_file_hardlink (self, mode, overwrite_mode, object_path, dest_path, cancellable, error) < 0)
+                goto out;
             }
           else if (priv->mode == OSTREE_REPO_MODE_ARCHIVE)
             {
@@ -2581,7 +2682,7 @@ ostree_repo_checkout_tree (OstreeRepo               *self,
                     goto out;
                 }
 
-              if (!checkout_file_from_input (dest_path, mode, file_info, xattrs, 
+              if (!checkout_file_from_input (dest_path, mode, overwrite_mode, file_info, xattrs, 
                                              content_input, cancellable, error))
                 goto out;
             }
@@ -2590,11 +2691,8 @@ ostree_repo_checkout_tree (OstreeRepo               *self,
               g_clear_object (&object_path);
               object_path = ostree_repo_get_object_path (self, checksum, OSTREE_OBJECT_TYPE_RAW_FILE);
 
-              if (link (ot_gfile_get_path_cached (object_path), ot_gfile_get_path_cached (dest_path)) < 0)
-                {
-                  ot_util_set_error_from_errno (error, errno);
-                  goto out;
-                }
+              if (!checkout_file_hardlink (self, mode, overwrite_mode, object_path, dest_path, cancellable, error) < 0)
+                goto out;
             }
         }
 
index 5206f37164e9a5af642df1ec2146d8ac84b5a1b4..25ef09ee61d76931b690c60a53ab62c734d85d06 100644 (file)
@@ -201,13 +201,19 @@ gboolean      ostree_repo_stage_commit (OstreeRepo   *self,
                                         GError      **error);
 
 typedef enum {
-  OSTREE_REPO_CHECKOUT_MODE_NONE,
-  OSTREE_REPO_CHECKOUT_MODE_USER
+  OSTREE_REPO_CHECKOUT_MODE_NONE = 0,
+  OSTREE_REPO_CHECKOUT_MODE_USER = 1
 } OstreeRepoCheckoutMode;
 
+typedef enum {
+  OSTREE_REPO_CHECKOUT_OVERWRITE_NONE = 0,
+  OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES = 1
+} OstreeRepoCheckoutOverwriteMode;
+
 gboolean
 ostree_repo_checkout_tree (OstreeRepo               *self,
                            OstreeRepoCheckoutMode    mode,
+                           OstreeRepoCheckoutOverwriteMode    overwrite_mode,
                            GFile                    *destination,
                            OstreeRepoFile           *source,
                            GFileInfo                *source_info,
index f4730725f469d49a43f31a877b1147906cab547d..fbdac1357d9a7e1050bf782f4eec47c8d8bd11ef 100644 (file)
 
 static gboolean user_mode;
 static char *subpath;
+static gboolean opt_union;
 
 static GOptionEntry options[] = {
   { "user-mode", 'U', 0, G_OPTION_ARG_NONE, &user_mode, "Do not change file ownership or initialze extended attributes", NULL },
   { "subpath", 0, 0, G_OPTION_ARG_STRING, &subpath, "Checkout sub-directory PATH", "PATH" },
+  { "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL },
   { NULL }
 };
 
@@ -99,6 +101,7 @@ ostree_builtin_checkout (int argc, char **argv, GFile *repo_path, GError **error
     goto out;
 
   if (!ostree_repo_checkout_tree (repo, user_mode ? OSTREE_REPO_CHECKOUT_MODE_USER : 0,
+                                  opt_union ? OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES : 0,
                                   destf, subtree, file_info, cancellable, error))
     goto out;
 
index 606ef38c872bde1853405a11e3978982472dde6e..2d55e2015a488c74b35d90850aea1580a668dfca 100755 (executable)
@@ -19,7 +19,7 @@
 
 set -e
 
-echo "1..27"
+echo "1..28"
 
 . libtest.sh
 
@@ -196,3 +196,13 @@ $OSTREE checkout --subpath /yet/another test2 checkout-test2-subpath
 cd checkout-test2-subpath
 assert_file_has_content tree/green "leaf"
 echo "ok checkout subpath"
+
+cd ${test_tmpdir}
+$OSTREE checkout --union test2 checkout-test2-union
+find checkout-test2-union | wc -l > union-files-count
+$OSTREE checkout --union test2 checkout-test2-union
+find checkout-test2-union | wc -l > union-files-count.new
+cmp union-files-count{,.new}
+cd checkout-test2-union
+assert_file_has_content ./yet/another/tree/green "leaf"
+echo "ok checkout union 1"